use std::str::FromStr;

use crate::{
    errors::cons_error,
    models::{
        club_form::ClubFormType,
        user::{NormalUserModel, UserRole},
        user_application::UserApplicationModelDTO,
        ClubFormStatusFlowType,
    },
    services::{club_service::ClubService, form_service::FormService},
    utils::authorize::{check_user_has_permission, check_user_has_permission_with_object_id},
    AppError, AppResult, PaginationQuery,
};
use actix_web::{
    web::{self, ServiceConfig},
    HttpResponse,
};
use wither::bson::oid::ObjectId;
mod req_struct {

    define_request_struct!(ChangeApplicationCtlReq {
        application_id: String,
        status_id: String
    });
    define_request_struct!(GetUserApplicationDetailReq {
        application_id: String
    });
    define_request_struct!(ArchiveApplicationReq {
        application_id: String
    });
    define_filter_request_struct!(SearchApplicationReq {
        search_all:Option<bool>
    });
}

/// ## change_application_status
/// 修改提交状态，对于草稿状态的提交只有创建者可以修改到下一个状态
/// 对于非草稿状态则只能由社长修改状态
async fn change_application_status(
    user: NormalUserModel,
    form_service: web::Data<FormService>,
    club_service: web::Data<ClubService>,
    req: web::Json<req_struct::ChangeApplicationCtlReq>,
) -> AppResult<HttpResponse> {
    let application_id = ObjectId::from_str(&req.application_id)?;

    let application = form_service
        .get_user_application_detail(application_id)
        .await?;
    if application
        .status
        .map(|f| f.flow_type == ClubFormStatusFlowType::Draft)
        .unwrap_or(false)
    {
        // 草稿状态下只需要校验操作者是创建者，草稿状态下只有初状态可以选择，所以不会出现
        if application.user_id.to_string() != user.id.to_string() {
            return Err(AppError::ConsError(cons_error::MSG_REQUIRE_AUTHORIZE));
        }
        let res = form_service
            .change_application_status(application_id, user, &req.status_id)
            .await?;
        return Ok(HttpResponse::Ok().json(UserApplicationModelDTO::from(res)));
    }

    // 非草稿状态则需要检查是否为社长或者管理员
    let form_model = form_service.form_detail(application.form_id).await?;
    if !check_user_has_permission(
        &user.clone().into(),
        form_model.club_id.to_string(),
        UserRole::LeaderOrAdmin as u8,
    ) {
        return Err(AppError::ConsError(cons_error::MSG_REQUIRE_AUTHORIZE));
    }
    let res = form_service
        .change_application_status(application_id, user, &req.status_id)
        .await?;
    if let Some(ref status) = res.status {
        if status.flow_type == ClubFormStatusFlowType::Success {
            tokio::spawn(async move {
                let form_detail = {
                    let res = form_service.form_detail(res.form_id).await;
                    if res.is_err() {
                        return;
                    }
                    res.unwrap()
                };
                if form_detail.form_type != ClubFormType::Join {
                    return;
                }
                // 如果是纳新表单，审批通过后将目标用户加入该社团
                match club_service
                    .upsert_user_in_club(res.user_id, form_model.club_id, UserRole::Student as u8)
                    .await
                {
                    Ok(_) => {}
                    Err(e) => {
                        log::error!("纳新表单通过后将用户加入社团失败，错误信息：{:?}", e);
                    }
                }
            });
        }
    }

    Ok(HttpResponse::Ok().json(UserApplicationModelDTO::from(res)))
}

async fn get_user_application_detail(
    user: NormalUserModel,
    form_service: web::Data<FormService>,
    req: web::Json<req_struct::GetUserApplicationDetailReq>,
) -> AppResult<HttpResponse> {
    let application_id = ObjectId::from_str(&req.application_id)?;
    let application_detail = form_service
        .get_user_application_detail(application_id)
        .await?;
    let form_detail = form_service.form_detail(application_detail.form_id).await?;
    if application_detail.user_id != user.id
        && !check_user_has_permission_with_object_id(
            user,
            form_detail.club_id,
            UserRole::LeaderOrAdmin as u8,
        )
    {
        return Err(AppError::ConsError(cons_error::MSG_REQUIRE_AUTHORIZE));
    }
    Ok(HttpResponse::Ok().json(UserApplicationModelDTO::from(application_detail)))
}

/// ## archive_application
/// 将申请归档
async fn archive_application(
    user: NormalUserModel,
    form_service: web::Data<FormService>,
    req: web::Json<req_struct::ArchiveApplicationReq>,
) -> AppResult<HttpResponse> {
    let application_id = ObjectId::from_str(&req.application_id)?;
    let application = form_service.application_detail(application_id).await?;
    let form = form_service.form_detail(application.form_id).await?;
    // 验证操作者是否为表单持有者
    if !check_user_has_permission_with_object_id(
        user.clone(),
        form.club_id,
        UserRole::LeaderOrAdmin,
    ) {
        return Err(AppError::ConsError(cons_error::MSG_REQUIRE_AUTHORIZE));
    }

    let res = form_service
        .archive_application(application_id, user)
        .await?;

    Ok(HttpResponse::Ok().json(UserApplicationModelDTO::from(res)))
}

async fn search_application(
    user: NormalUserModel,
    form_svr: web::Data<FormService>,
    req: web::Json<req_struct::SearchApplicationReq>,
    query: web::Query<PaginationQuery>,
) -> AppResult<HttpResponse> {
    let filter = req.get_filter_as_document_use_default().map(|mut doc| {
        if !(req.search_all.unwrap_or(false)
            && check_user_has_permission(&user.clone().into(), None, UserRole::Admin as u8))
        {
            doc.insert("user_id".to_string(), user.id);
        }
        doc
    });
    let res = form_svr
        .find_all_application(query.into_inner(), filter)
        .await?;
    Ok(HttpResponse::Ok().json(res.map_into::<UserApplicationModelDTO>()))
}

pub fn configure_application_controller(config: &mut ServiceConfig) {
    config
        .route("/detail", web::post().to(get_user_application_detail))
        .route("/change_status", web::post().to(change_application_status))
        .route("/archive", web::post().to(archive_application))
        .route("/search", web::post().to(search_application));
}
